import { pipe } from './functional.js';
import { ParseResponseError, HandleResponseContentError } from './errors.js';

// ===================== 常量定义 =====================
const HTTP_METHODS = Object.freeze({
  GET: 'GET',
  POST: 'POST',
  PUT: 'PUT',
  DELETE: 'DELETE',
});

const CONTENT_TYPES = Object.freeze({
  JSON: 'application/json',
  FORM_URLENCODED: 'application/x-www-form-urlencoded',
  FORM_DATA: 'multipart/form-data',
});

// ===================== 类型定义 =====================
/**
 * HTTP请求方法类型
 * @typedef {'get'|'post'|'put'|'delete'} HttpMethod
 */

/**
 * 接口配置类型
 * @typedef {Object} ApiConfig
 * @property {string} uri - 接口路径
 * @property {HttpMethod} [method] - 请求方法
 * @property {Record<string, string>} [headers] - 请求头
 */

/**
 * 服务端响应内容
 * @typedef {Object} ServerResponseContent
 * @property {number} code - 响应状态码
 * @property {string} msg - 消息内容
 * @property {'info'|'warn'|'error'|'success'|undefined} msgType - 消息类型
 * @property {any} [data] - 响应数据
 */

/**
 * 客户端基础选项
 * @typedef {Object} BaseOptions
 * @property {() => Record<string, string>} [makeHeaders] - 请求头
 * @property {((data: any) => any)[]} [defaultHandlers] - 默认响应处理函数集
 */

/**
 * 请求选项
 * @typedef {Object} RequestOptions
 * @property {any} [json] - JSON格式请求体
 * @property {FormData|URLSearchParams|Object} [data] - 表单数据
 * @property {Record<string, string|number>} [params] - URL查询参数
 * @property {(defaultHandlers: ((data: any) => any)[]) => ((data: any) => any)[]} [handlers] - （管道式）响应处理函数集，用于对响应数据进行处理
 */

// ===================== 核心函数 =====================
/**
 * 创建HTTP客户端工厂函数
 * @param {string} baseUrl - 基础URL。同作用域传空字符串，例如由 http://localhost:3000 分发的页面，此时传 baseUrl = ''
 * @param {BaseOptions} [baseOptions] - 客户端基础选项
 * @returns {function(ApiConfig, RequestOptions): Promise<any>} 请求函数
 */
export function createHttpClient(baseUrl, baseOptions) {
  /**
   * 执行HTTP请求
   * @param {ApiConfig} api - 接口配置
   * @param {RequestOptions} [options] - 请求选项
   * @returns {Promise<any>} 响应数据
   */
  return async (api, options = {}) => {
    // 1. 构造请求URL
    const url = buildRequestUrl(baseUrl, api.uri, options.params);

    // 2. 配置请求参数
    const requestConfig = buildRequestConfig(api, options, baseOptions);

    // 3. 执行网络请求
    const response = await executeFetchRequest(url, requestConfig);

    // 4. 处理响应内容
    const transformHandlers = options.handlers ?? ((v) => v);
    const handlers = transformHandlers(baseOptions.defaultHandlers ?? []);
    return handleResponse(response, ...handlers);
  };
}

// ===================== 请求构造函数 =====================
/**
 * 构建请求URL
 * @param {string} baseUrl - 基础URL
 * @param {string} uri - 接口路径
 * @param {Record<string, string|number>} [params] - 查询参数
 * @returns {string} 完整URL
 */
function buildRequestUrl(baseUrl, uri, params) {
  // 支持无主机名的baseUrl
  let tempSuffix = '';
  const protocols = ['http://', 'https://'];

  // 支持不指定主机名的baseUrl
  const hasProtocol = protocols.some((protocol) => baseUrl.startsWith(protocol));
  if (!hasProtocol) {
    tempSuffix = 'http://localhost';
  }

  const baseUrlValue = tempSuffix + baseUrl;
  const url = new URL(uri, baseUrlValue);

  if (params) {
    // 添加查询参数
    Object.entries(params).forEach(([key, value]) => {
      url.searchParams.append(key, String(value));
    });
  }

  // 返回时去除前面的主机名（如果需要）
  const urlString = url.toString();
  const finalUrl = tempSuffix ? baseUrl + urlString.slice(tempSuffix.length) : urlString;
  return finalUrl;
}

/**
 * 构建请求配置
 * @param {ApiConfig} api - 接口配置
 * @param {RequestOptions} options - 请求选项
 * @param {BaseOptions} [baseOptions] - 客户端基础选项
 * @returns {RequestInit} 请求配置
 */
function buildRequestConfig(api, options, baseOptions) {
  const { json, data } = options;
  const method = api.method || determineHttpMethod(options);

  // 合并基础headers和api特定headers
  const mergedHeaders = {
    ...(baseOptions?.makeHeaders ? baseOptions?.makeHeaders() : {}),
    ...(api.headers || {}),
  };
  const headers = new Headers(mergedHeaders);

  // 1. 设置请求方法
  const config = { method };

  // 2. 处理请求体
  if (json) {
    config.body = JSON.stringify(json);
    setContentType(headers, CONTENT_TYPES.JSON);
  } else if (data) {
    config.body = formatFormData(data);
    setContentType(headers, data instanceof FormData ? CONTENT_TYPES.FORM_DATA : CONTENT_TYPES.FORM_URLENCODED);
  }

  // 3. 设置请求头
  config.headers = headers;

  return config;
}

/**
 * 根据选项确定HTTP方法
 * @param {RequestOptions} options - 请求选项
 * @returns {HttpMethod} HTTP方法
 */
function determineHttpMethod(options) {
  if (options.json || options.data) return HTTP_METHODS.POST;
  return HTTP_METHODS.GET;
}

/**
 * 设置内容类型头
 * @param {Headers} headers - Headers对象
 * @param {string} contentType - 内容类型
 */
function setContentType(headers, contentType) {
  if (!headers.has('Content-Type')) {
    headers.set('Content-Type', contentType);
  }
}

/**
 * 格式化表单数据
 * @param {FormData|URLSearchParams|Object} data - 表单数据
 * @returns {FormData|URLSearchParams} 格式化后的数据
 */
function formatFormData(data) {
  if (data instanceof FormData || data instanceof URLSearchParams) {
    return data;
  }

  return Object.entries(data).reduce((formData, [key, value]) => {
    formData.append(key, String(value));
    return formData;
  }, new URLSearchParams());
}

// ===================== 请求执行函数 =====================
/**
 * 执行fetch请求
 * @param {string} url - 请求URL
 * @param {RequestInit} config - 请求配置
 * @returns {Promise<Response>} 响应对象
 */
async function executeFetchRequest(url, config) {
  const response = await fetch(url, config);

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  return response;
}

// ===================== 响应处理函数 =====================
/**
 * 处理响应
 * @param {Response} response - 响应对象
 * @param {((data: any) => any)[]} funcs - 响应对象处理函数集
 * @returns {Promise<any>} 处理后的数据
 */
async function handleResponse(response, ...funcs) {
  let content;
  try {
    content = await parseResponseContent(response);
  } catch (error) {
    throw new ParseResponseError(error);
  }

  if (funcs.length === 0) return content;

  try {
    const func = pipe(...funcs);
    content = await func(content);
  } catch (error) {
    throw new HandleResponseContentError(error);
  }
  return content;
}

/**
 * 解析响应内容
 * @param {Response} response - 响应对象
 * @returns {Promise<ServerResponseContent>} 解析后的内容
 */
async function parseResponseContent(response) {
  const contentType = response.headers.get('Content-Type') || '';

  if (contentType.includes('application/json')) {
    return response.json();
  }

  return response.text();
}
